
module DrunkenMaster
  
  class VisualComposite    
    NAME_SEPARATOR = '_'
    
    attr_accessor :children, :name, :parent, :name_prefix, :children_by_names, :context
    
    def initialize
      @children = []
      @children_by_names = {}
      @name_prefix = nil
      @open_elements = []
      @initialized = true
    end
    
    # Too often i forgot to call super() in initialize method.
    def assert_initialized
      unless @initialized 
        raise "Forgot to call super in initialize of #{self.class}."
      end
    end
    
    ## DSL shortcuts and goodies
    
    # Shortcut for add_child.
    def add vcomposite, &block
      assert_initialized
      parent_target = @open_elements.last || self
      parent_target.add_child vcomposite
      if block
        using(vcomposite, &block)
      end
      vcomposite
    end

    def add_many composites
      composites.flatten.collect { |vc| add vc }
    end
    
    def using composite, &block
      @open_elements << composite
        block.call composite
      @open_elements.pop
      composite
    end

    def anchor title, action = 'index', target = self, args = {}, html_options = {}
      options = {
        :href => url(action, target, args),
        :innerHTML => title
      }
      e(:a, options.merge(html_options))
    end

    
    # Shortcut for add_child, returns self.
    def << vcomposite
      add_child vcomposite
      self
    end
    
    # Shortcut for render_children.
    def render      
      render_children
    end    
    
    # Shortcut for callback.
    def call method
      callback method
    end
    
    # Shorthand for adding a new element.
    def e(*args, &block)
      add(Element.new(*args), &block)
    end
    
    # Shortcut for creating a new element.
    def el(*args)
      args[0] = args[0].to_s
      Element.new(*args)
    end

        
    # Performs instance eval of given block.
    # usage:
    # 
    #  e(:div, :class => 'post').in do
    #    e(:strong, post.subject)
    #    e(:p, post.body)
    #  end
    #
    def in(&block)
      raise 'Gimme a block, would ya?' unless block_given?
      instance_eval(&block)
    end
    
    
    ## real method implementations
    
    def children= array
      @children = []
      array.each do |child|
        add_child child
      end
      self
    end
    
    # Returns the current Context construction.
    def context
      if @context
        @context
      elsif parent
        parent.context
      end
    end
    
    
    # Recursively asks every child to idenify the url.
    def propagate_url
      #fixme #refactor this, make this into a recursion

      unless context.tail.is_a? Array
        raise "The first argument must be an array. Was #{context.tail.inspect}."
      end
      
      # forward compatability with ruby 1.9
      if public_methods.include?('handle_url') ||
          public_methods.include?(:handle_url)
        recognized_number = handle_url(context.tail)
        
        # if response was set, then we are finished
        return if @__response
        
        if recognized_number.is_a?(Array)
          @__recognized_chunks = recognized_number
          context.tail = context.tail[recognized_number.length, :last] || []
        elsif recognized_number.is_a?(Integer) && recognized_number > 0
          @__recognized_chunks = context.tail[0, recognized_number] || []
          context.tail = context.tail[recognized_number, :last] || []
        end

      end
      
      # propagate to children
      self.children.each do |child|
        #p "propagate to #{child.class}"
        child.propagate_url
      end 
    end
    
    
    # Returns array of parts of the url that were recognized by this widget.
    def recognized_chunks
      @__recognized_chunks
    end

    

    
    # Adds child composite.
    def add_child vcomposite
      if vcomposite.is_a? String
        vcomposite = VisualCompositeString.new(vcomposite)
      end
      if vcomposite.is_a?(Class) && 
          vcomposite.ancestors.include?(VisualComposite)
        vcomposite = vcomposite.new()
      end
      unless vcomposite.is_a? VisualComposite
        raise "Trying to add #{vcomposite.class} #{vcomposite.inspect}. But only VisualComposite can be added!"
      end
      if vcomposite.parent != nil
        #raise "This element #{vcomposite} already has a parent #{vcomposite.parent}."
      end
      @children << vcomposite
      vcomposite.parent = self
      _add_field vcomposite
    end
    
    def render_children
      (@children.collect{ |child|
        #child.name_prefix = whole_name
        child.render()
      })
    end
    
    # Distrubutes data across children.
    def recieve data
      @children.each { |el|
        el.recieve(data)
      }
    end
       
    # Dumps childrens' data.
    def dump data = {}
      @children.each { |child|
        if child.respond_to? :data
          data[child.whole_name] = child.data
        elsif child.respond_to? :dump
          child.dump data
        end
      }
      data
    end
    
    # The short sole name of the element.
    def given_name
      @name
    end
    
    # Then whole long combined name.
    def whole_name
      return nil if @name_prefix.nil? && @name.nil?
      [@name_prefix, @name].compact.join(NAME_SEPARATOR)
    end
    
    # If given_name present returns whole name.
    # This is used for whole name building.
    def name
      whole_name if @name
    end
    
    # Adds a line with the executing line number and file to remind myself
    # that i wanted to fix somethig there. FIXME doesnt work for me:)
    def fixme      
      puts "FIXME #{caller[0]}"
    end
 
    
   protected
  
    def expect_context
      raise 'No context.' unless context
    end

    
   private  
        
    def _add_field field
      if field.given_name
        unless @children_by_names[field.given_name]
          @children_by_names[field.given_name] = field
        else
          if $DEBUG 
            warn "field with name '#{field.given_name}' already exists in this container."
          end
        end
      end
      field
    end
    
  end

  # VisualComposite leaf kind of object to encapsulate string.
  class VisualCompositeString < VisualComposite
    def initialize string
      super()
      @string = string
    end
    
    def render
      [@string]
    end
  end
  
end